﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using Vortex.Wrapper.Platform;
using Vortex.Utils;
using Vortex.Wrapper.DInput;
using Vortex.Wrapper;
using Vortex.Debugging;

namespace Vortex.Input {

	///<summary>
	///Public keyboard interface for Vortex2D.NET engine.
	///It combines async input based on DirectInput and sync input based on Windows message queue. Note this listener intercepts all of window messages in application.
	///That means it should be used as global keyboard handler.
	///</summary>
	public class KeyboardListener : IKeyboard, IMessageFilter, IDisposable {
		///<summary>Value returned by GetKeyDownTime method if key is actually up</summary>
		public const float UNPRESSED_DURATION = -1.0f;

		private IntPtr _window;
		private DirectInputKeyboard8 _diKeyboard;
		private byte[] _currentStates = new byte[byte.MaxValue + 1];
		private byte[] _lastStates = new byte[byte.MaxValue + 1];
		private PerfTime[] _presstime = new PerfTime[byte.MaxValue + 1];
		private PerfTime _updateTime;

		#region Main methods
		
		public KeyboardListener(/*IntPtr windowHandle*/) {
			//_window = windowHandle;

			//init direct input here
			HResult result = DirectInput8.Instance.CreateKeyboard(out _diKeyboard);
			Watcher.AssertCritical(result, "Create DirectInput Keyboard Listener");

			//lets set cooperative level for this device
			HResult hr = _diKeyboard.SetCooperativeLevel(IntPtr.Zero, DICooperativeLevel.NonExclusive | DICooperativeLevel.Foreground);
			Watcher.AssertCritical(result, "Set cooperative level for Keyboard Listener");
		}

		/// <summary>
		/// Updates the keyboard state; specifying frame time delta you can drive.
		/// </summary>
		/// <param name="timeDelta">The time delta in seconds.</param>
		public void Update() {
			//update perf timer
			_updateTime = PerfTime.Now;

			//swap holding buffers
			byte[] temp = _lastStates;
			_lastStates = _currentStates;
			_currentStates = temp;

			//update states from device
			HResult aquireResult = _diKeyboard.Acquire();

			if (!_diKeyboard.ReadStates(_currentStates).IsSuccess) {
				Array.Clear(_currentStates, 0, _currentStates.Length);
			}

			//run through all of chars and update time of just pressed
			for (int n = 0; n <= (int)byte.MaxValue; ++n) {
				if (IsPressed((Key)n)) {
					_presstime[n] = _updateTime;
				}
			}
		}

		#endregion

		#region IKeyboard Members

		/// <summary>
		/// Determines whether the specified key is down.
		/// </summary>
		/// <param name="key">The key.</param>
		/// <returns>
		/// 	<c>true</c> if the specified key is down; otherwise, <c>false</c>.
		/// </returns>
		public bool IsDown(Key key) {
			return (_currentStates[(int)key] & 0x80) != 0;
		}

		/// <summary>
		/// Gets the time of specified key is in down state.
		/// </summary>
		/// <param name="key">The key.</param>
		/// <returns>Time in seconds how long specified key is down. If key is up then -1.0f (<c>UNPRESSED_DURATION</c> will be returned</returns>
		public float GetKeyDownTime(Key key) {
			if (IsDown(key)) {
				return (float)(_updateTime - _presstime[(int)key]).Seconds;
			} else {
				return UNPRESSED_DURATION;
			}
		}

		/// <summary>
		/// Determines whether the specified key is pressed (just changed state to down).
		/// </summary>
		/// <param name="key">The key.</param>
		/// <returns>
		/// 	<c>true</c> if the specified key is pressed; otherwise, <c>false</c>.
		/// </returns>
		public bool IsPressed(Key key) {
			return IsToggled(key) && IsDown(key);
		}

		/// <summary>
		/// Determines whether the specified key is released (just changed state to up from down).
		/// </summary>
		/// <param name="key">The key.</param>
		/// <returns>
		/// 	<c>true</c> if the specified key is released; otherwise, <c>false</c>.
		/// </returns>
		public bool IsReleased(Key key) {
			return IsToggled(key) && !IsDown(key);
		}

		/// <summary>
		/// Determines whether the specified key is toggled.
		/// </summary>
		/// <param name="key">The key.</param>
		/// <returns>
		/// 	<c>true</c> if the specified key is toggled; otherwise, <c>false</c>.
		/// </returns>
		public bool IsToggled(Key key) {
			return (_currentStates[(int)key] & 0x80) != (_lastStates[(int)key] & 0x80);
		}

		/// <summary>
		/// Determines whether some key combination was pressed.
		/// </summary>
		/// <param name="shiftKey">The shift key (it should be pressed before main key is going down).</param>
		/// <param name="mainKey">The main key.</param>
		/// <returns>
		/// 	<c>true</c> if specified combination was pressed; otherwise, <c>false</c>.
		/// </returns>
		public bool IsPressedCombination(Key shiftKey, Key mainKey) {
			return IsDown(shiftKey) && IsPressed(mainKey);
		}

		///<summary>Occurs when some key is down in focused window</summary>
		public event KeyActionHandler KeyDown;

		///<summary>Occurs when some key is up after it was down in focused window</summary>
		public event KeyActionHandler KeyUp;

		///<summary>Occurs when system decoded some character code from keyboard events</summary>
		public event CharActionHandler EnterChar;

		#endregion

		#region IMessageFilter Members

		/// <summary>
		/// Filters out a windows messages to intercept keyboard events and route them to user.
		/// </summary>
		/// <param name="m">The message to be dispatched.</param>
		/// <returns>
		/// <c>true</c> to filter the message and stop it from being dispatched; <c>false</c> to allow the message to continue to the next filter or control.
		/// </returns>
		public bool PreFilterMessage(ref Message m) {
			switch ((WM)m.Msg) {
				case WM.CHAR_:
					if (null != EnterChar) {
						EnterChar((char)(m.WParam.ToInt32() & 0xFFFF));
					}
					break;
				case WM.KEYDOWN:
					if (null != KeyDown) {
						KeyDown((VirtualKey)m.WParam.ToInt32());
					}
					break;
				case WM.KEYUP:
					if (null != KeyUp) {
						KeyUp((VirtualKey)m.WParam.ToInt32());
					}
					break;
			}
			return false;
		}

		#endregion

		#region IDisposable Members

		/// <summary>
		/// Performs keyboard internal data clean up
		/// </summary>
		public void Dispose() {
			;
		}

		#endregion
	}
}
